///////////////////////////////////////////////////////////////////////////////
// Class C_App_elf                                                           //
// Created by Hell Hibou (2008)                                              //
// Part of Wii Homebrew Launcher                                             //
// based on sources of William L. Pitts (2001) and Felix Domke (2004)        //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////


#include <string.h>
#include <ogcsys.h>

#include "app_elf.hpp"
#include "T_error.h"
#include "AppConfig.h"

#define ET_EXEC		2
#define EM_PPC		20
#define SHT_STRTAB	3
#define SHT_NOBITS	8
#define SHF_ALLOC	0x2

///////////////////////////////////////////////////////////////////////////////

bool C_App_elf::isElf (const void * elf)
{
    C_App_elf::eHeader *ehdr;

	ehdr = (C_App_elf::eHeader *) elf;

	if ( (ehdr->e_ident[0] != 0x7f || 
          ehdr->e_ident[1] != 'E'  || 
          ehdr->e_ident[2] != 'L'  || 
          ehdr->e_ident[3] != 'F') ) return false;

	if (ehdr->e_type != ET_EXEC) return false;
	if (ehdr->e_machine != EM_PPC) return false;
    return true;
};

void * C_App_elf::Load (const void * elf, const char * CommandLine, const u8 Action)
{
	C_App_elf::eHeader *ehdr;
	C_App_elf::sHeader *shdr;
	u8 *strtab = 0;
	u8 *image;
	int i;

	if (C_App_elf::isElf(elf) == false) { return NULL; }
	ehdr = (C_App_elf::eHeader *) elf;
	shdr = (C_App_elf::sHeader *) ((u8*)elf + ehdr->e_shoff + (ehdr->e_shstrndx * sizeof (C_App_elf::sHeader)));

	if (shdr->sh_type == SHT_STRTAB) { strtab = (u8 *) ((u8*)elf + shdr->sh_offset); }
	
	for (i = 0; i < ehdr->e_shnum; ++i) 
	{
		shdr = (C_App_elf::sHeader *) ((u8*)elf + ehdr->e_shoff + (i * sizeof (C_App_elf::sHeader)));
		if (!(shdr->sh_flags & SHF_ALLOC) || shdr->sh_addr == 0 || shdr->sh_size == 0) { continue; }
		shdr->sh_addr &= 0x3FFFFFFF;
		shdr->sh_addr |= 0x80000000;
		
		if (shdr->sh_addr < 0x80003F00)		{ return NULL; }
		if (shdr->sh_addr + shdr->sh_size > 0x81800000)	{ return NULL; }
		
		if (shdr->sh_type == SHT_NOBITS) 
		{
			if (Action == APP_ACTION_RUN) { memset ((void *) shdr->sh_addr, 0, shdr->sh_size); }
		} 
		else 
		{
			image = (u8 *) (u8*)elf + shdr->sh_offset;
			
		#ifndef APP_MODE_LITE	
			if (Action == APP_ACTION_PRELOAD) { SetLibogcArg (image, CommandLine); }
		#endif
			
			if (Action == APP_ACTION_RUN) { memcpy ((void *) shdr->sh_addr, (const void *) image, shdr->sh_size); }
		}
		
		if (Action == APP_ACTION_RUN) { DCFlushRangeNoSync ((void *) shdr->sh_addr, shdr->sh_size); }
	}
	
	return (void*) ((ehdr->e_entry & 0x3FFFFFFF) | 0x80000000);
}

///////////////////////////////////////////////////////////////////////////////
